/*
  扩展 setData 函数
  使用方法  绑定DOM
  也可以主动使用 主动使用 分为 写入 与 获取
  该方法已全局混入 Page | Component - $model , $modelEvent
示例:
const { $model , $modelEvent } = require("@/utils/model")
page({
    data:{
        person:{
            name:"wanghuan",
            age:[0,1,2]
        }
    },
    $model,
    $modelEvent
})

绑定 input事件
<input
  type="text"
  bindinput = "$modelEvent"
  data-modelPath = "person.name"
></input >

绑定点击或其他事件  需要指定 value
<view
  bindtap = "$modelEvent"
  data-modelPath = "person.name"
  data-modelValue = "test"
></view >

同时写入对个对象
<input
  type="text"
  bindinput = "$modelEvent"
  data-modelPath = "person.name|person.age[0]"
></input >

用函数设置变量的值
第一个参数是变量原始的值
$model({
    "person.age"( val ){
        return val + 1
    }
})
获取变量的值
获取一个变量的值
const ageVal =  $model("person.age")
获取多个变量的值
const vals =  $model(["person.name","person.age"])

*/
/**
 * @description: 仿照微信小程序setData函数(具备其所有特性) ， 并在其之上做了一些扩展。
 * @param {Object} keyObj //自定义密钥配置对象
 * @param {Object} modelConfig  [
 *                     , module // 模块路径会拼接在每个载入路径前面
 *                     , callback // 回调函数，第一个参数为所有载入的集合对象 第二个参数为所有载入对象path:value的集合对象]
 * @return {void}
 */
const comUtil = require('./common')
const mainUtil = require('./main')

function $model(keyObj, modelConfig) {
  const page = this
  const pageData = page.data
  let AsskeyObj
  let keyObjType
  // 载入模式  true === 载入  false === 获取
  let modelType = true
  if (keyObj) {
    AsskeyObj = {}
    keyObjType = comUtil.toType(keyObj)
    // 如果是对象模式   默认载入模式 载入
    if (keyObjType === 'Object') {
      AsskeyObj = keyObj
      // 字符串类型 或者 数组类型  载入模式  获取
    } else if (keyObjType === 'String') {
      AsskeyObj[keyObj] = function(ov) {
        return ov
      }
      modelType = false
    } else if (keyObjType === 'Array') {
      keyObj.forEach((keyPath) => {
        AsskeyObj[keyPath] = function(ov) {
          return ov
        }
      })
      modelType = false
    }
    if (Object.keys(AsskeyObj).length === 0) {
      throw Error('model object length is null')
    }
  } else {
    throw Error('the keyObj is must be required')
  }

  if (AsskeyObj) {
    // 保存需要写入的所有对象
    // 减少 setData 合并额外没有变动属性
    // setData-path 仅为小程序特性支持
    const modelAss_path = {}
    const modelHasCallback = modelConfig && comUtil.isType(modelConfig.callback, 'Function')
    Object.keys(AsskeyObj).forEach((keyNode) => {
      // 获取value
      const value = AsskeyObj[keyNode]
      // 2021/3/29 新增 value === function
      const valueIsFun = typeof value === 'function'
      let modelPath = keyNode
      // 判断模块是否存在 为写入路径拼接上模块路径
      if (modelConfig && modelConfig.module && typeof modelConfig.module === 'string') {
        modelPath = modelConfig.module + '.' + modelPath
      }
      // 如果存在分割符  , 或者 | 转换为数组
      // 消除空格
      const modelArr = comUtil.trimAll(modelPath).split(/[,|]/)
      // 对分割后的model进行循环写入
      modelArr.forEach((modelNode) => {
        // 如果value不是函数类型 结合setData函数特性  直接收集写入路径   阻断后续查找原始值
        if (!valueIsFun) {
          modelAss_path[modelNode] = value
          return
        }
        // 转换modelNode上的数组元素为统一分隔符
        const modelAga = mainUtil.formatModelPath(modelNode)
        // 进行深度写入
        const modelLeve = modelAga.split('.')
        let copyData = pageData
        for (let j = 0, l = modelLeve.length; j < l; j++) {
          const iterKey = modelLeve[j]
          if (iterKey) {
            const iterObj = copyData[iterKey]
            if (j === l - 1) {
              // 如果value为一个函数  则把 之前的value 作为函数的第一个值
              // 函数 this  指向调用 page对象
              const finalModelVal = copyData[iterKey]
              if (valueIsFun) {
                copyData[iterKey] = value.call(page, finalModelVal)
              } else {
                copyData[iterKey] = value
              }
              // 载入路径收集  ### 仅小程序setData api特性支持
              modelAss_path[modelNode] = copyData[iterKey]
              break
            } else {
              copyData = iterObj
            }
          } else {
            throw Error('the leve is not be Null')
          }
        }
      })
    })

    // 转换为原生api路径载入
    if (page && typeof page.setData === 'function') {
      if (modelType) {
        page.setData(modelAss_path)
      }
      // 成功回调函数
      if (modelHasCallback) {
        wx.nextTick(function() {
          modelConfig.callback(modelAss_path)
        })
      }
      // 如果写入模式返回 写入路径与值 对象
      if (modelType) {
        return modelAss_path
      } else {
        const modelAssVals = Object.values(modelAss_path)
        // 如果是 获取模式
        // 字符串模式  直接返回获取值
        if (keyObjType === 'String') {
          return modelAssVals[0]
        } else {
          // 数组模式   按顺序返回获取值数组集合
          return modelAssVals
        }
      }
    }
  } else {
    console.warn('missing necessary parameters')
  }
}

// model in Event
function $modelEvent(event) {
  let AsskeyObj
  if (event) {
    // data - prop 不区大小写
    const modelValueKey = 'modelvalue'
    const modelPathKey = 'modelpath'
    const eventData = event.currentTarget.dataset
    const edModelPath = eventData[modelPathKey]

    if (edModelPath) {
      AsskeyObj = {}
      let modelVal
      // 判断 data-modelValue  属性是否存在
      // 当  ( data-modelValue ="" ) 时也是需要载入
      if (!eventData.hasOwnProperty(modelValueKey)) {
        // 输入事件
        if (event.type === 'input') {
          modelVal = event.detail.value
        }
      } else {
        modelVal = eventData[modelValueKey]
      }
      AsskeyObj[edModelPath] = modelVal
    } else {
      throw Error('the event modelPath is must be required')
    }
    $model.call(this, AsskeyObj)
  } else {
    throw Error('Event Params is not be null')
  }
}

module.exports = {
  $model,
  $modelEvent
}
